

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Net;


namespace Invites
{

    public class InvitesGame : Microsoft.Xna.Framework.Game
    {
        int bumperIndex = 0;
        int[] latence = new int[] { 0, 50, 100, 250, 500 };
        float[] loss = new float[] { 0, 0, 0, 0, 0 };

        Boat myBoat, remoteBoat;

        Vector2 oldPos,newPos;
        float tween = 0;

        const int screenWidth = 1067;
        const int screenHeight = 600;

        const int maxGamers = 16;
        const int maxLocalGamers = 4;

        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
        SpriteFont font;


        GamePadState curGamepad,prevState;

        NetworkSession networkSession;

        PacketWriter packetWriter = new PacketWriter();
        PacketReader packetReader = new PacketReader();
        string errorMessage;

  
        public InvitesGame()
        {
            graphics = new GraphicsDeviceManager(this);

            graphics.PreferredBackBufferWidth = screenWidth;
            graphics.PreferredBackBufferHeight = screenHeight;

            Content.RootDirectory = "Content";

            Components.Add(new GamerServicesComponent(this));

            NetworkSession.InviteAccepted += InviteAcceptedEventHandler;
        }



        protected override void LoadContent()
        {
            spriteBatch = new SpriteBatch(GraphicsDevice);
            font = Content.Load<SpriteFont>("Font");
        }


        protected override void Update(GameTime gameTime)
        {
            HandleInput();

            if (networkSession == null)
            {
                UpdateMenuScreen();
            }
            else
            {
                UpdateNetworkSession(gameTime);
            }

            base.Update(gameTime);
        }


        void UpdateMenuScreen()
        {
            if (IsActive)
            {
                if (Gamer.SignedInGamers.Count == 0)
                {
                    Guide.ShowSignIn(maxLocalGamers, false);
                }
                else if (IsPressed(Buttons.A))
                {
                    CreateSession();
                }
                else if (IsPressed(Buttons.B))
                {
                    JoinSession();
                }
            }
        }

        void CreateSession()
        {
            DrawMessage("Creating session...");

            try
            {
                networkSession = NetworkSession.Create(NetworkSessionType.PlayerMatch, maxLocalGamers, maxGamers);
                HookSessionEvents();
                
            }
            catch (Exception error)
            {
                errorMessage = error.Message;
            }
        }
        void JoinSession()
        {
            DrawMessage("Joining session...");

            try
            {
                // Search for sessions.
                using (AvailableNetworkSessionCollection availableSessions =NetworkSession.Find(NetworkSessionType.PlayerMatch, maxLocalGamers, null))
                {
                    if (availableSessions.Count == 0)
                    {
                        errorMessage = "No network sessions found.";
                        return;
                    }

                    // Join the first session we found.
                    networkSession = NetworkSession.Join(availableSessions[0]);
                    HookSessionEvents();
                }
            }
            catch (Exception error)
            {
                errorMessage = error.Message;
            }
        }

       
        void HookSessionEvents()
        {
            networkSession.GamerJoined += GamerJoinedEventHandler;
            networkSession.SessionEnded += SessionEndedEventHandler;
            myBoat = new Boat(0, Content, screenWidth, screenHeight);
        }
        void InviteAcceptedEventHandler(object sender, InviteAcceptedEventArgs e)
        {
            DrawMessage("Joining session from invite...");

            // Leave the current network session.
            if (networkSession != null)
            {
                networkSession.Dispose();
                networkSession = null;
            }

            try
            {
                // Join a new session in response to the invite.
                networkSession = NetworkSession.JoinInvited(maxLocalGamers);
                HookSessionEvents();
            }
            catch (Exception error)
            {
                errorMessage = error.Message;
            }
        }
        void GamerJoinedEventHandler(object sender, GamerJoinedEventArgs e)
        {
            //int gamerIndex = networkSession.AllGamers.IndexOf(e.Gamer);
            //e.Gamer.Tag = new Boat(gamerIndex, Content, screenWidth, screenHeight);
            if (networkSession.RemoteGamers.Count > 0)
            {
                remoteBoat = new Boat(1, Content, screenWidth, screenHeight);
            }
        }
        void SessionEndedEventHandler(object sender, NetworkSessionEndedEventArgs e)
        {
            errorMessage = e.EndReason.ToString();

            networkSession.Dispose();
            networkSession = null;
        }


        void UpdateNetworkSession(GameTime gg)
        {
           

            WriteLocalGamer(networkSession.LocalGamers[0], gg);
          
       
            networkSession.SimulatedLatency = TimeSpan.FromMilliseconds(latence[bumperIndex]);
            networkSession.SimulatedPacketLoss = loss[bumperIndex];

            networkSession.Update();

            if (networkSession == null)    return;

            ReadIncomingPackets(networkSession.LocalGamers[0]);

        }


        void WriteLocalGamer(LocalNetworkGamer gamer,GameTime gg)
        {
            // Look up what tank is associated with this local player.
            //Boat localTank = gamer.Tag as Boat;

            // Update the tank.
            ReadTankInputs(myBoat, gamer.SignedInGamer.PlayerIndex);
            myBoat.Update();

            if (networkSession.RemoteGamers.Count > 0 && (int)gg.TotalGameTime.Milliseconds%250==0)
            {
                // Write the tank state into a network packet.
                packetWriter.Write(myBoat.Position);
                packetWriter.Write(myBoat.TankRotation);
                packetWriter.Write(myBoat.TurretRotation);

                // Send the data to everyone in the session.
                gamer.SendData(packetWriter, SendDataOptions.InOrder, networkSession.RemoteGamers[0]);
            }
        }

        void ReadIncomingPackets(LocalNetworkGamer gamer)
        {
            // if (networkSession.RemoteGamers.Count > 0) 
            // Keep reading as long as incoming packets are available.
            while (gamer.IsDataAvailable)
            {
                NetworkGamer sender;

                // Read a single packet from the network.
                gamer.ReceiveData(packetReader, out sender);

                // Discard packets sent by local gamers: we already know their state!
                //if (sender.IsLocal)  continue;

                // Look up the tank associated with whoever sent this packet.
                //Boat remoteTank = sender.Tag as Boat;

                // Read the state of this tank from the network packet.
                //remoteTank.Position = packetReader.ReadVector2();
                tween = 0;
                oldPos = remoteBoat.Position;
                newPos = packetReader.ReadVector2();

                remoteBoat.TankRotation = packetReader.ReadSingle();
                remoteBoat.TurretRotation = packetReader.ReadSingle();
            }
        }



        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.DarkGreen);

            if (networkSession == null)
            {
                // If we are not in a network session, draw the
                // menu screen that will let us create or join one.
                DrawMenuScreen();
            }
            else
            {
                // If we are in a network session, draw it.
                DrawNetworkSession();
            }

            base.Draw(gameTime);
        }


        void DrawMenuScreen()
        {
            string message = string.Empty;

            if (!string.IsNullOrEmpty(errorMessage)) message += "Error:\n" + errorMessage.Replace(". ", ".\n") + "\n\n";

            message += "A = create session\n" +  "B = join session";

            spriteBatch.Begin();
            spriteBatch.DrawString(font, message, new Vector2(160, 160), Color.White);
            spriteBatch.End();
        }
        void DrawNetworkSession()
        {
            spriteBatch.Begin();

            Gamer gamer = networkSession.LocalGamers[0];
            //Boat boat = gamer.Tag as Boat;

            myBoat.Draw(spriteBatch);

            // Draw a gamertag label.
            string label = gamer.Gamertag;
            Color labelColor = Color.Black;
            Vector2 labelOffset = new Vector2(100, 150);

            label += " (host)";
            spriteBatch.DrawString(font, label, myBoat.Position, labelColor, 0, labelOffset, 0.6f, SpriteEffects.None, 0);

            if (networkSession.RemoteGamers.Count > 0)
            {
                gamer = networkSession.RemoteGamers[0];
              
                tween += .0666f; if (tween > 1) tween = 1;
                remoteBoat.Position = Vector2.Lerp(oldPos, newPos, tween);
                remoteBoat.Draw(spriteBatch);

                label = gamer.Gamertag;
                labelColor = Color.Black;
                labelOffset = new Vector2(100, 150);

                spriteBatch.DrawString(font, label, remoteBoat.Position, labelColor, 0, labelOffset, 0.6f, SpriteEffects.None, 0);
            }

            spriteBatch.DrawString(font, "Delay:" + latence[bumperIndex] + "  Loss:" + loss[bumperIndex], new Vector2(40, 0), Color.White);
            spriteBatch.End();
        }


        void DrawMessage(string message)
        {
            if (!BeginDraw()) return;

            GraphicsDevice.Clear(Color.DarkRed);

            spriteBatch.Begin();
            spriteBatch.DrawString(font, message, new Vector2(160, 160), Color.White);
            spriteBatch.End();

            EndDraw();
        }




        private void HandleInput()
        {
            prevState = curGamepad;
            curGamepad = GamePad.GetState(PlayerIndex.One);

            // Check for exit.
            if (IsActive && IsPressed(Buttons.Back))   Exit();

            if (curGamepad.Buttons.RightShoulder == ButtonState.Pressed && prevState.Buttons.RightShoulder == ButtonState.Released)
            {
                bumperIndex++;
                if (bumperIndex > latence.Length - 1) bumperIndex = latence.Length - 1;

            }
            if (curGamepad.Buttons.LeftShoulder == ButtonState.Pressed && prevState.Buttons.LeftShoulder == ButtonState.Released)
            {
                bumperIndex--;
                if (bumperIndex < 1) bumperIndex = 0;
            }
        }

        bool IsPressed(Buttons button)
        {
            return (curGamepad.IsButtonDown(button));
        }

        void ReadTankInputs(Boat boat, PlayerIndex playerIndex)
        {
            // Read the gamepad.
            GamePadState gamePad = GamePad.GetState(playerIndex);

            Vector2 tankInput = gamePad.ThumbSticks.Left;
            Vector2 turretInput = gamePad.ThumbSticks.Right;

            // Normalize the input vectors.
            if (tankInput.Length() > 1) tankInput.Normalize();

            if (turretInput.Length() > 1) turretInput.Normalize();

            // Store these input values into the tank object.
            boat.TankInput = tankInput;
            boat.TurretInput = turretInput;
        }



    }


    #region Entry Point


    static class Program
    {
        static void Main()
        {
            using (InvitesGame game = new InvitesGame())
            {
                game.Run();
            }
        }
    }

    #endregion
}
